Skip to content

fix: don't close shared session store in runtime.Close#2879

Open
dgageot wants to merge 1 commit into
docker:mainfrom
dgageot:board/2c638431e2df4d72
Open

fix: don't close shared session store in runtime.Close#2879
dgageot wants to merge 1 commit into
docker:mainfrom
dgageot:board/2c638431e2df4d72

Conversation

@dgageot
Copy link
Copy Markdown
Member

@dgageot dgageot commented May 22, 2026

The TUI was failing to open /sessions after switching to a session with a different working directory, with the error "failed to load sessions: sql: database is closed". This happened because when the TUI swapped in a new runtime for the different working directory, it canceled the old runtime's context and ran cleanup, which called rt.Close()r.sessionStore.Close(). However, the spawned new runtime was given the same SQLite store (via createSessionSpawner reusing rt.SessionStore()), so closing it from the old runtime broke every subsequent read against that store.

The fix moves session store ownership to the backend layer, which now owns its lifecycle independently. The Runtime.Close() contract is updated to make explicit that the session store is owned by the embedder and may be shared. localBackend now lazily opens the SQLite store on first CreateSession call (via sync.Once) and closes it once in a new backend.Close() method. The runtime no longer closes the store it was given, and the TUI defers b.Close() to clean up resources when the entire session ends.

Fixes #2872

@dgageot dgageot requested a review from a team as a code owner May 22, 2026 15:34
Copy link
Copy Markdown

@docker-agent docker-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟢 APPROVE

The fix correctly addresses the shared session store lifecycle issue. The pattern for lazy initialization in is a sound approach, is properly deferred in , and the contract update is clearly documented. No resource leaks, double-close, or use-after-close issues were identified in the changed code.

Copy link
Copy Markdown

@docker-agent docker-agent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

The fix correctly moves session-store ownership to the backend layer, resolving the use-after-close bug (#2872). One potential data race was found in the new localBackend.Close() method.

Comment thread cmd/root/backend.go
}

func (b *localBackend) Close() error {
if b.store == nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Potential data race: Close() reads b.store outside sync.Once

localBackend.Close() reads b.store directly (line 130) without any synchronization, while sessionStore() writes b.store inside storeOnce.Do(...). The Go memory model only guarantees that the write to b.store inside the Do closure is visible after Do returns — it does not provide a happens-before guarantee for concurrent reads of b.store that happen outside of Do.

If Close() races with an in-progress storeOnce.Do() call (e.g., a session is still being initialized when shutdown begins), the Go race detector would flag this.

Suggested fix — either use storeOnce.Do to query the store in Close() as well, or protect b.store with a mutex:

func (b *localBackend) Close() error {
    b.storeOnce.Do(func() {}) // ensure the write, if any, has completed
    if b.store == nil {
        return nil
    }
    return b.store.Close()
}

Or, more robustly, add a sync.Mutex to guard access to b.store in both sessionStore and Close.

@aheritier aheritier added area/sessions For features/issues/fixes related to session lifecycle (resume, persistence, export) kind/fix PR fixes a bug (maps to fix: commit prefix) labels May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions For features/issues/fixes related to session lifecycle (resume, persistence, export) kind/fix PR fixes a bug (maps to fix: commit prefix)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

failed to load sessions: sql: database is closed

3 participants